#
# fusion.db.crawl
#
# Copyright 2008 Helsinki Institute for Information Technology
# and the authors.
#
# Authors: Ken Rimey <rimey@hiit.fi>
#

# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

from __future__ import with_statement

import time
import logging
from random import shuffle

from fusion.db.db import DB

class Crawler(object):
    def __init__(self, path):
        self.path = path

    def loop(self, throttle=50):
        self.db = DB(self.path, create=False)
        try:
            while True:
                try:
                    t0 = time.clock()
                    self.update()
                    t1 = time.clock()
                    time.sleep(max(2, throttle * (t1 - t0)))
                except Exception:
                    logging.exception('Exception in crawler:')
                    time.sleep(60)
        finally:
            self.db.close()

    def update(self):
        result = self.pick_unsubscribed_iid()
        if result:
            iid, description = result
            logging.info('Crawler subscribing to %s: %s', description, iid)
            self.db.subscribe(iid)

    def pick_unsubscribed_iid(self):
        with self.db.transaction():
            try:
                me = self.db.get_my_iid()
            except LookupError:
                return None

            # XXX Also consider memberships of friends.
            return (self.pick_one(self.list_friends(me), 'friend')
                    or self.pick_one(self.list_memberships(me), 'group')
                    or self.pick_one(self.list_friends_of_friends(me),
                                     'friend of friend')
                    or self.pick_one(self.list_fellow_group_members(me),
                                     'fellow group member'))

    def pick_one(self, iids, description):
        for iid in shuffled(iids):
            if not self.db.get_info(iid):
                return iid, description

    def list_friends(self, iid):
        return self.db.list_friends(iid, verify=False)

    def list_members(self, iid):
        return self.db.list_members(iid, verify=False)

    def list_memberships(self, iid):
        return self.db.list_memberships(iid, verify=False)

    def list_fellow_group_members(self, iid):
        result = set()
        for group in self.list_memberships(iid):
            result.update(self.list_members(group))

        result.discard(iid)
        return list(result)

    def list_friends_of_friends(self, iid):
        friends = self.list_friends(iid)

        result = set()
        for friend in friends:
            result.update(self.list_friends(friend))

        result.discard(iid)
        result.difference_update(friends)
        return list(result)

def shuffled(items):
    items = list(items)
    shuffle(items)
    return items
